use jni::objects::{AutoLocal, JList, JObject, JValue};

use markdown_elements::{Block, CheckableListItem, ListItem, Row, TableAlign};

pub(crate) fn generate_android_heading<'a>(
    env: &'_ mut jni::JNIEnv<'a>,
    level: usize,
    content: &'_ markdown_elements::TextList<'_>,
) -> JObject<'a> {
    let level = JValue::Int(level as i32);
    let blocks = generate_android_block(env, content);
    let ans = env
        .new_object(
            "com/lly/markdown/Heading",
            "(ILjava/util/List;)V",
            &[level, JValue::Object(&blocks)],
        )
        .unwrap();
    ans
}

pub(crate) fn generate_android_code<'a>(
    env: &'_ mut jni::JNIEnv<'a>,
    content: &'_ str,
) -> JObject<'a> {
    let content = env.new_string(content).unwrap();
    let ans = env
        .new_object(
            "com/lly/markdown/Code",
            "(Ljava/lang/String;)V",
            &[JValue::Object(&content)],
        )
        .unwrap();
    ans
}
pub(crate) fn generate_android_error<'a>(
    env: &'_ mut jni::JNIEnv<'a>,
    content: &'_ str,
) -> JObject<'a> {
    let content = env.new_string(content).unwrap();
    let ans = env
        .new_object(
            "com/lly/markdown/Error",
            "(Ljava/lang/String;)V",
            &[JValue::Object(&content)],
        )
        .unwrap();
    ans
}

pub static ARRAYLIST_CLASS: &str = "java/util/ArrayList";
pub(crate) fn generate_android_text<'a>(
    env: &'_ mut jni::JNIEnv<'a>,
    content: &'_ markdown_elements::TextList<'_>,
) -> JObject<'a> {
    let blocks = generate_android_block(env, content);
    let ans = env
        .new_object(
            "com/lly/markdown/Text",
            "(Ljava/util/List;)V",
            &[JValue::Object(&blocks)],
        )
        .unwrap();
    ans
}
pub(crate) fn generate_android_block<'a>(
    env: &'_ mut jni::JNIEnv<'a>,
    content: &'_ markdown_elements::TextList<'_>,
) -> JObject<'a> {
    let ans = env.new_object(ARRAYLIST_CLASS, "()V", &[]).unwrap();
    let list = JList::from_env(env, &ans).unwrap();
    for ele in content.iter() {
        let it = to_android_block(ele, env);
        let _ = list.add(env, &it);
    }
    ans
}

fn to_android_block<'a>(ele: &'_ Block<'_>, env: &'_ mut jni::JNIEnv<'a>) -> JObject<'a> {
    match ele {
        Block::Image { alt, link } => {
            let doc: AutoLocal<JObject> = env.auto_local(env.new_string(alt).unwrap().into());
            let alt = JValue::from(&doc);

            let link: AutoLocal<JObject> = env.auto_local(env.new_string(link).unwrap().into());
            let link = JValue::from(&link);

            let link = env.new_object(
                "com/lly/markdown/Image",
                "(Ljava/lang/String;Ljava/lang/String;)V",
                &[alt, link],
            );
            link.unwrap()
        }
        Block::Link { doc, link } => {
            let doc: AutoLocal<JObject> = env.auto_local(env.new_string(doc).unwrap().into());
            let doc = JValue::from(&doc);

            let link: AutoLocal<JObject> = env.auto_local(env.new_string(link).unwrap().into());
            let link = JValue::from(&link);

            let link = env.new_object(
                "com/lly/markdown/Link",
                "(Ljava/lang/String;Ljava/lang/String;)V",
                &[doc, link],
            );
            link.unwrap()
        }
        Block::Plaint { text, span: _ } => {
            let text: AutoLocal<JObject> = env.auto_local(env.new_string(text).unwrap().into());
            let text = JValue::from(&text);
            let text = env.new_object("com/lly/markdown/Plaint", "(Ljava/lang/String;)V", &[text]);
            text.unwrap()
        }
        Block::Decorater(item, decorater) => {
            let block = generate_android_block(env, item);
            let ans = env.new_object(
                "com/lly/markdown/Decorater",
                "(Ljava/util/List;I)V",
                &[JValue::Object(&block), JValue::Int(decorater.value())],
            );
            ans.unwrap()
        }
        Block::BR => {
            let ans = env
                .get_static_field(
                    "com/lly/markdown/Line",
                    "INSTANCE",
                    "Lcom/lly/markdown/Line;",
                )
                .unwrap();
            ans.l().unwrap()
        }
    }
}

pub(crate) fn generate_android_list<'local>(
    env: &'_ mut jni::JNIEnv<'local>,
    items: &'_ [ListItem<'_>],
    is_order: &bool,
) -> JObject<'local> {
    let blocks = env.new_object(ARRAYLIST_CLASS, "()V", &[]).unwrap();
    let list = JList::from_env(env, &blocks).unwrap();
    for ele in items {
        let item = generate_android_list_item(env, ele);
        let _ = list.add(env, &item);
    }
    let ans = env
        .new_object(
            "com/lly/markdown/MList",
            "(Ljava/util/List;Z)V",
            &[JValue::Object(&blocks), JValue::Bool(*is_order as u8)],
        )
        .unwrap();
    ans
}

pub(crate) fn generate_android_list_item<'local>(
    env: &'_ mut jni::JNIEnv<'local>,
    item: &'_ ListItem<'_>,
) -> JObject<'local> {
    // data class MListItem(val indent: Int, val content: List<Block>, val children: List<MListItem>)
    let indent = JValue::Int(item.indent as i32);
    let content = generate_android_block(env, &item.content);
    let children = env.new_object(ARRAYLIST_CLASS, "()V", &[]).unwrap();
    let list = JList::from_env(env, &children).unwrap();
    if let Some(children) = &item.children {
        for ele in children.iter() {
            let item = generate_android_list_item(env, ele);
            let _ = list.add(env, &item);
        }
    }
    let ans = env
        .new_object(
            "com/lly/markdown/MListItem",
            "(ILjava/util/List;Ljava/util/List;)V",
            &[indent, JValue::Object(&content), JValue::Object(&children)],
        )
        .unwrap();
    ans
}

pub(crate) fn generate_android_checkable_list<'local>(
    env: &'_ mut jni::JNIEnv<'local>,
    items: &'_ [CheckableListItem<'_>],
) -> JObject<'local> {
    let blocks = env.new_object(ARRAYLIST_CLASS, "()V", &[]).unwrap();
    let list = JList::from_env(env, &blocks).unwrap();
    for ele in items {
        let item = generate_android_checkable_list_item(env, ele);
        let _ = list.add(env, &item);
    }
    let ans = env
        .new_object(
            "com/lly/markdown/MCheckableList",
            "(Ljava/util/List;)V",
            &[JValue::Object(&blocks)],
        )
        .unwrap();
    ans
}

pub(crate) fn generate_android_checkable_list_item<'local>(
    env: &'_ mut jni::JNIEnv<'local>,
    item: &'_ CheckableListItem<'_>,
) -> JObject<'local> {
    // data class MListItem(val indent: Int, val content: List<Block>, val children: List<MListItem>)
    let indent = JValue::Int(item.indent as i32);
    let content = generate_android_block(env, &item.content);
    let children = env.new_object(ARRAYLIST_CLASS, "()V", &[]).unwrap();
    let list = JList::from_env(env, &children).unwrap();
    if let Some(children) = &item.children {
        for ele in children.iter() {
            let item = generate_android_checkable_list_item(env, ele);
            let _ = list.add(env, &item);
        }
    }
    let ans = env
        .new_object(
            "com/lly/markdown/MCheckableListItem",
            "(ILjava/util/List;Ljava/util/List;Z)V",
            &[
                indent,
                JValue::Object(&content),
                JValue::Object(&children),
                JValue::Bool(item.is_checked as u8),
            ],
        )
        .unwrap();
    ans
}

pub(crate) fn generate_android_table<'local>(
    env: &mut jni::JNIEnv<'local>,
    heads: &Row,
    rows: &[Row],
    aligns: &[TableAlign],
) -> JObject<'local> {
    let o_rows = env.new_object(ARRAYLIST_CLASS, "()V", &[]).unwrap();
    let list = JList::from_env(env, &o_rows).unwrap();
    let row = generate_android_table_row(env, heads);
    let _ = list.add(env, &row);
    for ele in rows {
        let row = generate_android_table_row(env, ele);
        let _ = list.add(env, &row);
    }
    let aligns = generate_android_table_aligns(env, aligns);
    let ans = env
        .new_object(
            "com/lly/markdown/Table",
            "(Ljava/util/List;Ljava/util/List;)V",
            &[JValue::Object(&o_rows), JValue::Object(&aligns)],
        )
        .unwrap();
    ans
}

fn generate_android_table_row<'local>(env: &mut jni::JNIEnv<'local>, row: &Row) -> JObject<'local> {
    let o_cells = env.new_object(ARRAYLIST_CLASS, "()V", &[]).unwrap();
    let list = JList::from_env(env, &o_cells).unwrap();
    for ele in &row.cells {
        let cell = generate_android_block(env, ele);
        let _ = list.add(env, &cell);
    }
    let row = env
        .new_object(
            "com/lly/markdown/Table$Row",
            "(Ljava/util/List;)V",
            &[JValue::Object(&o_cells)],
        )
        .unwrap();
    row
}

fn generate_android_table_aligns<'local>(
    env: &mut jni::JNIEnv<'local>,
    aligns: &[TableAlign],
) -> JObject<'local> {
    let o_cells = env.new_object(ARRAYLIST_CLASS, "()V", &[]).unwrap();
    let list = JList::from_env(env, &o_cells).unwrap();
    for ele in aligns {
        let cell = ele.value();
        let cell = env
            .new_object("java/lang/Integer", "(I)V", &[JValue::Int(cell)])
            .unwrap();
        let _ = list.add(env, &cell);
    }
    o_cells
}
